import collections
from typing import Tuple, List, Iterator
import pygame

from src.cfg import (
    DOWN, UP, LEFT, RIGHT, NODE_WIDTH, NODE_HEIGHT, 
    SCREEN_WIDTH, SCREEN_HEIGHT
)
class Snake:

    def __init__(self, color: Tuple[int]) -> None:
        self.direction = DOWN
        self.color = color
        self.body = collections.deque()

    def init_snake(self, length: int, init_pos: Tuple[int]) -> None:
        assert init_pos[0] % NODE_WIDTH == 0
        assert init_pos[1] % NODE_HEIGHT == 0
        self.clear()
        for _ in range(length):
            self.body.append(
                pygame.Rect(
                    init_pos[0],
                    init_pos[1],
                    NODE_WIDTH,
                    NODE_HEIGHT)
            )

    def add_node(self) -> None:
        if self.body:
            left, top = self.body[0].left, self.body[0].top
            node = pygame.Rect(left, top, NODE_WIDTH, NODE_HEIGHT)
            if self.direction == LEFT:
                node.left -= NODE_WIDTH
            elif self.direction == RIGHT:
                node.left += NODE_WIDTH
            elif self.direction == UP:
                node.top -= NODE_HEIGHT
            elif self.direction == DOWN:
                node.top += NODE_HEIGHT
            self.body.appendleft(node)  

    def clear(self) -> None:
        self.body.clear()
        
    def del_node(self) -> None:
        if self.length > 0:
                self.body.pop()

    @property
    def positions(self) -> List[Tuple[int]]:
        return [(n.left, n.top) for n in self]

    def __iter__(self) -> Iterator:
        return iter(self.body)

    def is_dead(self) -> bool:
        if not self.positions:
            return False
        if self.head_pos[0] not in range(SCREEN_WIDTH):
            return True
        if self.head_pos[1] not in range(SCREEN_HEIGHT):
            return True
        return False

    def move(self) -> None:
        self.del_node()
        self.add_node()

    @property
    def length(self) -> int:
        return len(self.body)

    @property
    def direction(self) -> str:
        return self._direction

    @direction.setter
    def direction(self, d: str) -> None:
        self._direction = d

    def change_direction(self, direction: str) -> None:
        if direction is None or direction == self.direction:
            return
        LR = {LEFT, RIGHT}
        UD = {UP, DOWN}
        if direction in LR.union(UD):
            if (direction in LR and self.direction in LR) or \
               (direction in UD and self.direction in UD):
                return
            self.direction = direction

    @property
    def tail_pos(self) -> Tuple[int]:
        return self.body[-1].left, self.body[-1].top

    @property
    def head_pos(self) -> Tuple[int]:
        assert self.body
        return self.body[0].left, self.body[0].top
    
    def draw(self, sc: pygame.Surface) -> None:
        for rect in self.body:
            pygame.draw.rect(sc, self.color, rect, 2)
